﻿namespace WebDevUtilities.Common.Graphic
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Text;
    using System.Linq;

    public class Sprite
    {
        private Dictionary<int, Image> images;
        private Dictionary<int, string> cssClassNames;
        private LayoutProperties layoutProp;

        public Sprite(LayoutProperties _layoutProp)
        {
            images = new Dictionary<int, Image>();
            cssClassNames = new Dictionary<int, string>();
            layoutProp = _layoutProp;
        }

        public void Create()
        {
            GetData(out images, out cssClassNames);

            using (FileStream outputSpriteFile = File.Create(layoutProp.OutputSpriteFilePath))
            using (TextWriter cssFile = new StreamWriter(layoutProp.OutputCssFilePath))
            {
                Image resultSprite = null;
                cssFile.WriteLine(".{0} {{ background-image: url('{1}'); background-color: transparent; background-repeat: no-repeat; }}",
                    layoutProp.SpriteClassname,
                    relativeSpriteImagePath(layoutProp.OutputSpriteFilePath, layoutProp.OutputCssFilePath));

                switch (layoutProp.Layout)
                {
                    case "Automatic":
                        resultSprite = generateAutomaticLayout(cssFile);
                        break;
                    case "Horizontal":
                        resultSprite = generateHorizontalLayout(cssFile);
                        break;
                    case "Vertical":
                        resultSprite = generateVerticalLayout(cssFile);
                        break;
                    case "Rectangular":
                        resultSprite = generateRectangularLayout(cssFile);
                        break;
                    default:
                        break;
                }
                resultSprite.Save(outputSpriteFile, ImageFormat.Png);
            }
        }

        /// <summary>
        /// Creates dictionary of images from the given paths and dictionary of CSS classnames from the image filenames.
        /// </summary> 
        /// <param name="inputFilePaths">Array of input file paths.</param>
        /// <param name="images">Dictionary of images to be inserted into the output sprite.</param>
        /// <param name="cssClassNames">Dictionary of CSS classnames.</param>
        private void GetData(out Dictionary<int, Image> images, out Dictionary<int, string> cssClassNames)
        {
            images = new Dictionary<int, Image>();
            cssClassNames = new Dictionary<int, string>();

            for (int i = 0; i < layoutProp.InputFilePaths.Length; i++)
            {
                Image img = Image.FromFile(layoutProp.InputFilePaths[i]);
                images.Add(i, img);
                string[] splittedFilePath = layoutProp.InputFilePaths[i].Split('\\');
                cssClassNames.Add(i, splittedFilePath[splittedFilePath.Length - 1].Split('.')[0]);
            }
        }

        private List<Module> CreateModules()
        {
            List<Module> modules = new List<Module>();
            foreach (int i in images.Keys)
                modules.Add(new Module(i, images[i], layoutProp.DistanceBetweenImages));
            return modules;
        }

        //CSS line
        private string CssLine(string cssClassName, Rectangle rectangle)
        {
            string line = "." + cssClassName + " { width: " + rectangle.Width.ToString() + "px; height: " + rectangle.Height.ToString() +
                "px; background-position: " + (-1 * rectangle.X).ToString() + "px " + (-1 * rectangle.Y).ToString() + "px; }";
            return line;
        }

        //Relative sprite image file path
        private string relativeSpriteImagePath(string outputSpriteFilePath, string outputCssFilePath)
        {
            string[] splittedOutputCssFilePath = outputCssFilePath.Split('\\');
            string[] splittedOutputSpriteFilePath = outputSpriteFilePath.Split('\\');

            int breakAt = 0;
            for (int i = 0; i < splittedOutputCssFilePath.Length; i++)
                if (i < splittedOutputSpriteFilePath.Length && splittedOutputCssFilePath[i] != splittedOutputSpriteFilePath[i])
                {
                    breakAt = i;
                    break;
                }

            string relativePath = "";
            for (int i = 0; i < splittedOutputCssFilePath.Length - breakAt - 1; i++)
                relativePath += "../";
            relativePath += String.Join("/", splittedOutputSpriteFilePath, breakAt, splittedOutputSpriteFilePath.Length - breakAt);

            return relativePath;
        }

        //Automatic layout
        private Image generateAutomaticLayout(TextWriter cssFile)
        {
            var sortedByArea = from m in CreateModules()
                               orderby m.Width * m.Height descending
                               select m;
            List<Module> moduleList = sortedByArea.ToList<Module>();
            Placement placement = Algorithm.Greedy(moduleList);

            //Creating an empty result image.
            Image resultSprite = new Bitmap(placement.Width - layoutProp.DistanceBetweenImages + 2 * layoutProp.MarginWidth,
                placement.Height - layoutProp.DistanceBetweenImages + 2 * layoutProp.MarginWidth);
            Graphics graphics = Graphics.FromImage(resultSprite);

            //Drawing images into the result image in the original order and writing CSS lines.
            int i = 0;
            foreach (Module m in placement.Modules)
            {
                m.Draw(graphics, layoutProp.MarginWidth);
                Rectangle rectangle = new Rectangle(m.X + layoutProp.MarginWidth, m.Y + layoutProp.MarginWidth,
                    m.Width - layoutProp.DistanceBetweenImages, m.Height - layoutProp.DistanceBetweenImages);
                cssFile.WriteLine(CssLine(layoutProp.UseCustomClassname ? layoutProp.CustomClassname + "-" + i : cssClassNames[m.Name], rectangle));
                i++;
            }

            return resultSprite;
        }

        //Horizontal layout
        private Image generateHorizontalLayout(TextWriter cssFile)
        {
            //Calculating result image dimension.
            int width = 0;
            foreach (Image image in images.Values)
                width += image.Width + layoutProp.DistanceBetweenImages;
            width = width - layoutProp.DistanceBetweenImages + 2 * layoutProp.MarginWidth;
            int height = images[0].Height + 2 * layoutProp.MarginWidth;

            //Creating an empty result image.
            Image resultSprite = new Bitmap(width, height);
            Graphics graphics = Graphics.FromImage(resultSprite);

            //Initial coordinates.
            int actualXCoordinate = layoutProp.MarginWidth;
            int yCoordinate = layoutProp.MarginWidth;

            //Drawing images into the result image, writing CSS lines and increasing X coordinate.
            foreach (int i in images.Keys)
            {
                Rectangle rectangle = new Rectangle(actualXCoordinate, yCoordinate, images[i].Width, images[i].Height);
                graphics.DrawImage(images[i], rectangle);
                cssFile.WriteLine(CssLine(layoutProp.UseCustomClassname ? layoutProp.CustomClassname + "-" + i : cssClassNames[i], rectangle));
                actualXCoordinate += images[i].Width + layoutProp.DistanceBetweenImages;
            }

            return resultSprite;
        }

        //Vertical layout
        private Image generateVerticalLayout(TextWriter cssFile)
        {
            //Calculating result image dimension.
            int height = 0;
            foreach (Image image in images.Values)
                height += image.Height + layoutProp.DistanceBetweenImages;
            height = height - layoutProp.DistanceBetweenImages + 2 * layoutProp.MarginWidth;
            int width = images[0].Width + 2 * layoutProp.MarginWidth;

            //Creating an empty result image.
            Image resultSprite = new Bitmap(width, height);
            Graphics graphics = Graphics.FromImage(resultSprite);

            //Initial coordinates.
            int actualYCoordinate = layoutProp.MarginWidth;
            int xCoordinate = layoutProp.MarginWidth;

            //Drawing images into the result image, writing CSS lines and increasing Y coordinate.
            foreach (int i in images.Keys)
            {
                Rectangle rectangle = new Rectangle(xCoordinate, actualYCoordinate, images[i].Width, images[i].Height);
                graphics.DrawImage(images[i], rectangle);
                cssFile.WriteLine(CssLine(layoutProp.UseCustomClassname ? layoutProp.CustomClassname + "-" + i : cssClassNames[i], rectangle));
                actualYCoordinate += images[i].Height + layoutProp.DistanceBetweenImages;
            }

            return resultSprite;
        }

        private Image generateRectangularLayout(TextWriter cssFile)
        {
            //Calculating result image dimension.
            int imageWidth = images[0].Width;
            int imageHeight = images[0].Height;
            int width = layoutProp.ImagesInRow * (imageWidth + layoutProp.DistanceBetweenImages) -
                layoutProp.DistanceBetweenImages + 2 * layoutProp.MarginWidth;
            int height = layoutProp.ImagesInColumn * (imageHeight + layoutProp.DistanceBetweenImages) -
                layoutProp.DistanceBetweenImages + 2 * layoutProp.MarginWidth;

            //Creating an empty result image.
            Image resultSprite = new Bitmap(width, height);
            Graphics graphics = Graphics.FromImage(resultSprite);

            //Initial coordinates.
            int actualYCoordinate = layoutProp.MarginWidth;
            int actualXCoordinate = layoutProp.MarginWidth;

            //Drawing images into the result image, writing CSS lines and increasing coordinates.
            for (int i = 0; i < layoutProp.ImagesInColumn; i++)
            {
                for (int j = 0; (i * layoutProp.ImagesInRow) + j < images.Count && j < layoutProp.ImagesInRow; j++)
                {
                    Rectangle rectangle = new Rectangle(actualXCoordinate, actualYCoordinate, imageWidth, imageHeight);
                    graphics.DrawImage(images[i * layoutProp.ImagesInRow + j], rectangle);
                    cssFile.WriteLine(CssLine(layoutProp.UseCustomClassname ? layoutProp.CustomClassname + "-" + i : cssClassNames[i * layoutProp.ImagesInRow + j], rectangle));
                    actualXCoordinate += imageWidth + layoutProp.DistanceBetweenImages;
                }
                actualYCoordinate += imageHeight + layoutProp.DistanceBetweenImages;
                actualXCoordinate = layoutProp.MarginWidth;
            }

            return resultSprite;
        }
    }
}